use crate::controller::error::ObjectHasBeenReferenceError;
use crate::{controller, db};
use axum::Json;
use axum::extract::rejection::JsonRejection;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use wanmesh_common::dto::ResponseCode;
use wanmesh_common::error::{ResponseError, ResponseErrorAny};
use wanmesh_common::validator::ValidationErrors;

#[derive(thiserror::Error, Debug)]
#[error("http_status_code={status_code}, {response}")]
pub struct HttpResponseError {
    pub status_code: StatusCode,
    pub response: ResponseError,
}

impl HttpResponseError {
    pub fn unauthorized(msg: String) -> Self {
        HttpResponseError {
            status_code: StatusCode::UNAUTHORIZED,
            response: ResponseError {
                code: ResponseCode::UNAUTHORIZED,
                message: msg,
                backtrace: Default::default(),
            },
        }
    }
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("{0}")]
    HttpResponseError(#[from] HttpResponseError),
    #[error("{0}")]
    ResponseError(#[from] ResponseError),
    #[error("{0}")]
    ResponseErrorAny(#[from] ResponseErrorAny),
    #[error("{0}")]
    ValidationErrors(#[from] ValidationErrors),
    #[error("{0}")]
    ObjectHasBeenReferenceError(#[from] ObjectHasBeenReferenceError),
    #[error("{0}")]
    JsonRejection(#[from] JsonRejection),
    #[error("{0}")]
    DbError(#[from] db::DbErr),
    #[error("{0}")]
    Other(#[from] anyhow::Error),
}

impl IntoResponse for Error {
    fn into_response(self) -> Response {
        let mut status_code = StatusCode::OK;
        let response: wanmesh_common::dto::Response<()> = match self {
            Error::HttpResponseError(e) => {
                log::warn!("{}", e);
                status_code = e.status_code;
                (&e.response).into()
            }
            Error::ResponseError(e) => {
                log::warn!("{}", e);
                (&e).into()
            }
            Error::ResponseErrorAny(e) => {
                log::warn!("{}", e);
                wanmesh_common::dto::Response::new(e.code, e.message)
            }
            Error::ValidationErrors(e) => {
                wanmesh_common::dto::Response::new(ResponseCode::BAD_REQUEST, e.source.to_string())
            }
            Error::ObjectHasBeenReferenceError(e) => {
                wanmesh_common::dto::Response::new(ResponseCode::BAD_REQUEST, e.to_string())
            }
            Error::JsonRejection(e) => {
                wanmesh_common::dto::Response::new(ResponseCode::BAD_REQUEST, e.to_string())
            }
            Error::DbError(e) => {
                log::error!("{}", e);
                wanmesh_common::dto::Response::internal_error()
            }
            Error::Other(e) => {
                log::error!("{:?}", e);
                wanmesh_common::dto::Response::internal_error()
            }
        };
        (status_code, Json(response)).into_response()
    }
}

impl From<controller::error::Error> for Error {
    fn from(value: controller::error::Error) -> Self {
        match value {
            controller::error::Error::ResponseError(e) => Error::ResponseError(e),
            controller::error::Error::ResponseErrorAny(e) => Error::ResponseErrorAny(e),
            controller::error::Error::ObjectHasBeenReferenceError(e) => {
                Error::ObjectHasBeenReferenceError(e)
            }
            controller::error::Error::DbError(e) => Error::DbError(e),
            controller::error::Error::Other(e) => Error::Other(e),
        }
    }
}
